#include "LogMod.h"
#include <stdarg.h>
#include <stdio.h>
#include <chrono>
#include <iomanip>
#include <sstream>
#include <assert.h>

//#include <sys/timeb.h>


#ifdef USE_FILESYSTEM
#   warning "remenber use -lstdc++fs"
#   if __cplusplus > 201402L
#   include <filesystem>
namespace fs = std::filesystem;
#   else
#   include <experimental/filesystem>
namespace fs = std::experimental::filesystem; 
#   endif
#else
#   ifdef _WIN32
#   include <io.h>
#   include <direct.h>
#   else
#   include <unistd.h>
#   include <dirent.h>
#   include <sys/stat.h>
#   endif
#endif


namespace tool {
namespace Log {

static constexpr const char* g_LevelStr[] = { "TRACE", "DEBUG","INFO ","WARN ","ERROR","SUCCESS","UNDEFINED" };

using namespace std::chrono;

int64_t TimeStamp()
{
    return time_point_cast<milliseconds>(system_clock::now()).time_since_epoch().count();
    //std::experimental::filesystem::end();
}

std::string  TimeToString(int64_t timeStamp, const std::string& format)
{
    std::ostringstream ss;
    std::time_t tms = (timeStamp /= 1000);
    //ss<<std::put_time(std::localtime(&tms),format.data());
    ss << std::put_time(std::localtime(&tms), format.data());
    return ss.str();
}

std::string MsTimeStamp()
{
    auto now = system_clock::now();
    auto tp = system_clock::to_time_t(now);
    const auto& t = *std::localtime(&tp);
    std::string str;
    str.resize(32);
    int size = snprintf(&str[0], str.size(), "%4d-%02d-%02d %02d:%02d:%02d.%03lld",
        t.tm_year + 1900, t.tm_mon + 1, t.tm_mday,
        t.tm_hour, t.tm_min, t.tm_sec,
        time_point_cast<milliseconds>(now).time_since_epoch().count() % 1000);
    str.resize(size);
    return str;
}

// TODO...
// 这样子无法输出固定长度的时间戳
std::string  TimeMsToString(int64_t timeStamp, const std::string& format)
{
    std::string ret = format;
    std::string::size_type pos = {};

    int tm_msec = timeStamp % 1000;
    std::time_t tms = (timeStamp /= 1000);

    std::tm tt = *std::localtime(&tms);
#define REPLACE_STR(pattern,value) \
	do{\
	if ((pos = ret.find(pattern))!=std::string::npos)\
		ret.replace(pos, sizeof(pattern)-1, std::to_string(value));\
	}while(false)

    REPLACE_STR("%y", tt.tm_year);
    REPLACE_STR("%m", tt.tm_mon);
    REPLACE_STR("%d", tt.tm_mday);

    REPLACE_STR("%H", tt.tm_hour);
    REPLACE_STR("%M", tt.tm_min);
    REPLACE_STR("%S", tt.tm_sec);
    REPLACE_STR("%g", tm_msec);
#undef REPLACE_STR

    return ret;
}

#ifdef USE_FILESYSTEM
void CreatePathForFile(const std::string& fileName)
{
    fs::path parentPath = fs::path(fileName).parent_path();
    if (!fs::exists(parentPath))
        fs::create_directories(parentPath);
    //if (access(fileName.data(),0) !=0 ) {//this means folder not exist
    //    bool ret = mkdir(fileName.data()) == 0;
    //}
}

std::vector<std::string> GetFilesFromDir(const std::string& dirName)
{
    fs::path path(dirName);
    std::vector<std::string> stringList;
    for (auto& fentry : fs::directory_iterator(path)) {
        fs::path subPath = fentry.path();
        std::string subName = subPath.filename().string();
        if (fs::is_directory(subPath)) {
            if (subName != "." && subName != "..") {
                //reccurrence
            }
        }
        else {
            stringList.push_back(subName);
        }
    }
    return stringList;
}
}
#else
void CreatePathForFile(const std::string& fileName)
{
    //get dir name from file name
    std::string dirName;
    auto pos = fileName.find_last_of('/');
    if (pos != std::string::npos) {//find it
        dirName = fileName.substr(0, pos);
    }
    else if ((pos = fileName.find_last_of('\\')) != std::string::npos) {
        dirName = fileName.substr(0, pos);
    }
    else {
        dirName = fileName;
    }

    if (access(dirName.data(), 0) != 0) {//this means folder not exist
#ifdef _WIN32
        bool ret = mkdir(dirName.data()) == 0;
#else
        bool ret = mkdir(dirName.data(),0777) == 0;
#endif
    }
}

/*!
 * \brief GetFilesFromDir
 * \param dirName
 * \return
 */
std::vector<std::string> GetFilesFromDir(const std::string& dirName)
{
    std::vector<std::string> ret;
#ifdef _WIN32
    _finddata_t findData;
    auto handle = _findfirst(dirName.data(), &findData);    // 查找目录中的第一个文件
    if (handle == -1)
        return ret;
    do
    {
        if (findData.attrib & _A_SUBDIR) {
            if ((strcmp(findData.name, ".") != 0)
                && (strcmp(findData.name, "..") != 0)
                )    // 是否是子目录并且不为"."或".."
                ;//迭代遍历
        }
        else {
            ret.push_back(findData.name);
        }
    } while (_findnext(handle, &findData) == 0);    // 查找目录中的下一个文件
    _findclose(handle);    // 关闭搜索句柄
#else
    auto pos = dirName.find_last_of('/');
    std::string dir = dirName.substr(0,pos+1);
    std::string file = dirName.substr(pos+1);
    LOG_D("dir:%s file:%s",dir.data(),file.data());
    DIR* dirInfo = opendir(dir.data());
    if(dirInfo!=nullptr){
        while(dirent* entry = readdir(dirInfo)){
            if(entry->d_type == DT_REG){//普通文件
                ret.push_back(dir+entry->d_name);
            }
        }
        closedir(dirInfo);
    }
#endif
    return ret;
}
#endif


#ifndef USE_ASYNC_LOG

CSysLog::CSysLog()
    :m_file(nullptr)
    , m_toScreen(true)
    , m_toFile(false)
{ }

CSysLog::~CSysLog()
{
    if (m_file != nullptr)
        fclose(m_file);
}

void CSysLog::setScreenState(bool isOpen)
{
    std::unique_lock<std::mutex> mtxLock(m_mutex);
    m_toScreen = isOpen;
}

void CSysLog::setFileState(bool isOpen, std::string fileName)
{
    std::unique_lock<std::mutex> mtxLock(m_mutex);
    if (isOpen) {//open
        if (m_file != nullptr)
            fclose(m_file);
        if (fileName.empty()) {
            fileName = TimeToString(TimeStamp(), "./log/log_%Y%m%d_%H%M%S.log");
        }

        CreatePathForFile(fileName);

        m_file = fopen(fileName.data(), "wb+");
        if (!m_file) {
            printf("Error:Cannot create log file(%s)\n", fileName.data());
        }
        else {
            m_toFile = true;
        }
    }
    else {//close
        fclose(m_file);
        m_file = nullptr;
        m_toFile = false;
    }
}

void CSysLog::installMessageCallback(MessageCallback callback)
{
    std::unique_lock<std::mutex> mtxLock(m_mutex);
    m_callback = std::move(callback);
}

void CSysLog::ShowMessage(const char* szFormat, ...)
{
    char cBuff[256] = {};
    int strSize = 0;
    va_list arglist;
    // 处理变长参数
    va_start(arglist, szFormat);
    strSize += vsnprintf(cBuff, sizeof(cBuff), szFormat, arglist);
    va_end(arglist);

    std::unique_lock<std::mutex> mtxLock(m_mutex);//锁住
    if (m_toScreen)
        printf("%s", cBuff);
    if (m_toFile)
        fprintf(m_file, "%s", cBuff);
    if (m_callback)
        m_callback(cBuff, strSize);
}

void CSysLog::ShowMessage(int nLogLevelIn, const char* functionName, int line, const char* szFormat, ...)
{
    char buffer[256];
    int prefixSize = snprintf(buffer, sizeof(buffer), "[%s TYPE:%s FUNC:%s LINE:%d ] ",
        MsTimeStamp().data(),
        GetLogType(nLogLevelIn),
        functionName,
        line);
    // 处理变长参数
    va_list arglist;//添加日志信息
    va_start(arglist, szFormat);
    int dataSize = vsnprintf(buffer + prefixSize, sizeof(buffer) - prefixSize, szFormat, arglist);
    va_end(arglist);

    std::unique_lock<std::mutex> mtxLock(m_mutex);//锁住
    if (m_toScreen)
        printf("%s\n", buffer);
    if (m_toFile)
        fprintf(m_file, "%s\n", buffer);
    if (m_callback)
        m_callback(buffer, prefixSize + dataSize);
}
#else

std::string LogFormat::scFormat;

std::string LogFormat::GetPrefix() const
{
    std::string formator = scFormat;
    std::string::size_type pos = {};

#define REPLACE_STR(pattern,word) \
	do{\
	if ((pos = formator.find(pattern))!=std::string::npos)\
		formator.replace(pos, sizeof(pattern)-1, word);\
	}while(false)
    if (m_time == nullptr) {
        REPLACE_STR("{time}", MsTimeStamp());
    }
    else {
        REPLACE_STR("{time}", m_time);
    }

    REPLACE_STR("{file}", m_file);
    REPLACE_STR("{line}", std::to_string(m_line));
    REPLACE_STR("{function}", m_function);
    REPLACE_STR("{type}", g_LevelStr[m_level]);
    return formator;
#undef REPLACE_STR
}

AsyncLog::AsyncLog()
{
    m_currBuf = std::make_unique<Buffer>();
    m_nextBuf = std::make_unique<Buffer>();
    m_currBuf->bzero();
    m_nextBuf->bzero();
    m_vecBuf.reserve(16);
    setScreenState(true);
#ifdef __linux__
    setColorState(true);
#endif
}

AsyncLog::~AsyncLog()
{
    Stop();
    DataClose();
}

void AsyncLog::setRollCondition(int recordTime_sec, int64_t recordSize_byte)
{
    m_rollTime_sec = recordTime_sec;
    m_rollSize_byte = recordSize_byte;
}

void AsyncLog::DataWrite(const char* data, int size)
{
    {
        const auto& caller = std::get<0>(m_toDefault);
        if (caller)
            caller(data, size);
    }
    {
        const auto& caller = std::get<0>(m_toFile);
        if (caller)
            caller(data, size);
    }
    {
        const auto& caller = std::get<0>(m_toInstaller);
        if (caller)
            caller(data, size);
    }
}

void AsyncLog::DataFlush()
{
    {
        const auto& caller = std::get<1>(m_toDefault);
        if (caller)
            caller();
    }
    {
        const auto& caller = std::get<1>(m_toFile);
        if (caller)
            caller();
    }
    {
        const auto& caller = std::get<1>(m_toInstaller);
        if (caller)
            caller();
    }
}

void AsyncLog::DataClose()
{
    {
        const auto& caller = std::get<2>(m_toDefault);
        if (caller)
            caller();
    }
    {
        const auto& caller = std::get<2>(m_toFile);
        if (caller)
            caller();
    }
    {
        const auto& caller = std::get<2>(m_toInstaller);
        if (caller)
            caller();
    }
}

void AsyncLog::setScreenState(bool state)
{
    std::unique_lock<std::mutex> mtxLock(m_mutex);
    if (state) {
        std::get<0>(m_toDefault) = [](const char* data, int size) {
            fwrite(data, size, 1, stdout);
        };
        std::get<1>(m_toDefault) = []() {fflush(stdout);};
        std::get<2>(m_toDefault) = []() {fflush(stdout);};
    }
    else {
        m_toDefault = LogFunctor({}, {}, {});
    }

}

void AsyncLog::setFileState(bool state, std::string fileName)
{
    std::unique_lock<std::mutex> mtxLock(m_mutex);

    //first close 
    const auto& closer = std::get<2>(m_toFile);
    if (closer)
        closer();

    if (state) {//open

        if (fileName.empty()) {
            fileName = TimeToString(TimeStamp(), "./log/log_%Y%m%d_%H%M%S.log");
        }

        CreatePathForFile(fileName);

        FILE* fdWrite = fopen(fileName.data(), "wb+");
        if (!fdWrite) {
            fprintf(stderr, "Error:Cannot create log file(%s)\n", fileName.data());
        }
        else {
            std::get<0>(m_toFile) = [fdWrite](const char* data, int size) {
                fwrite(data, size, 1, fdWrite);
            };
            std::get<1>(m_toFile) = [fdWrite]() {fflush(fdWrite);};
            std::get<2>(m_toFile) = [fdWrite]() {fclose(fdWrite);};
        }
    }
    else {//close
        m_toFile = LogFunctor({}, {}, {});
    }
}




namespace ColorType{

namespace detail {

typedef long long hash64;
namespace const_expr
{
constexpr hash64 prime = 0x100000001B3ull;
constexpr hash64 basis = 0xCBF29CE484222325ull;
}
constexpr hash64 make_hash_static(char const* str)
{
    return (*str) ? (*(str + 1)) ? (((*str) * const_expr::prime + const_expr::basis) ^ make_hash_static(str + 1)) : ((*str) * const_expr::prime + const_expr::basis) : 0;
}

constexpr hash64 operator "" _hash(char const* p, size_t)
{
    return make_hash_static(p);
}
template<char>using charDummy = char;
template <int N>
constexpr char at(const char* a) { return a[N]; }
template<int... dummy>
struct F
{
    const char Name[sizeof...(dummy) + 1];
    const hash64  Hash;
    const int Length;
    const int Size;

    constexpr F(const char* a) : Name{ at<dummy>(a)..., 0 }, Length(sizeof...(dummy)), Size(sizeof...(dummy) + 1), Hash(a[0] * const_expr::prime + const_expr::basis){}
    constexpr F(hash64 h, charDummy<dummy>... a) : Name{ a..., 0 }, Length(sizeof...(dummy)), Size(sizeof...(dummy) + 1), Hash(h){}
    constexpr F(const F& a) : Name{ a.Name[dummy]..., 0 }, Length(a.Length), Size(a.Size), Hash(a.Hash){}

    template<int... dummyB>
    constexpr F<dummy..., sizeof...(dummy) + dummyB...> operator + (F<dummyB...> b)const
    {
        return{ this->Hash ^ b.Hash,this->Name[dummy]..., b.Name[dummyB]... };
    }
    operator const char* ()const { return Name; }
};

template<int I>
struct get_string
{
    constexpr static auto g(const char* a) -> decltype(get_string<I - 1>::g(a) + F<0>(a + I))
    {
        return get_string<I - 1>::g(a) + F<0>(a + I);
    }
};

template<>
struct get_string<0>
{
    constexpr static F<0> g(const char* a)
    {
        return{ a };
    }
};

template<int I>
constexpr auto string_literal(const char(&a)[I]) -> decltype(get_string<I - 2>::g(a))
{
    return get_string<I - 2>::g(a);
}

}//namespace detail

using namespace detail;

constexpr const char StartCode[] = "\x1B[";
constexpr const char StopCode[] = "m";

constexpr const char CloseAll          []= "0";
constexpr const char Highlight         []= "1";
constexpr const char Underline         []= "4";
constexpr const char Twinkle           []= "5";
constexpr const char ReverseDisplay    []= "7";
constexpr const char Blanking          []= "8"; 
                                       
constexpr const char FrontBlack        []= "30";
constexpr const char FrontRed          []= "31";
constexpr const char FrontGreen        []= "32";
constexpr const char FrontYellow       []= "33";
constexpr const char FrontBlue         []= "34";
constexpr const char FrontPurple       []= "35";
constexpr const char FrontCyan         []= "36";
constexpr const char FrontWhite        []= "37";
                                       
constexpr const char BackBlack         []= "40";
constexpr const char BackRed           []= "41";
constexpr const char BackGreen         []= "42";
constexpr const char BackYellow        []= "43";
constexpr const char BackBlue          []= "44";
constexpr const char BackPurple        []= "45";
constexpr const char BackCyan          []= "46";
constexpr const char BackWhite         []= "47";

constexpr const char ClearScreen[] = "2J";
constexpr const char HideCursor[] = "?25l";
constexpr const char ShowCursor[] = "?25h";
//nA 光标上移n行
//nB 光标下移n行
//nC 光标右移n行
//nD 光标左移n行
//y;xH设置光标位置
//2J 清屏
//K 清除从光标到行尾的内容
//s 保存光标位置
//u 恢复光标位置

constexpr auto LiteralOfTrace = string_literal(StartCode) + string_literal(FrontWhite) + string_literal(StopCode);
constexpr auto LiteralOfDebug = string_literal(StartCode) + string_literal(FrontWhite) + string_literal(";") + string_literal(Highlight) + string_literal(StopCode);
constexpr auto LiteralOfInfo  = string_literal(StartCode) + string_literal(FrontGreen) + string_literal(StopCode);
constexpr auto LiteralOfWarn  = string_literal(StartCode) + string_literal(FrontPurple) + string_literal(StopCode);
constexpr auto LiteralOfError = string_literal(StartCode) + string_literal(FrontRed) + string_literal(Highlight) + string_literal(StopCode);
constexpr auto LiteralOfSuccess = string_literal(StartCode) + string_literal(FrontBlack) + string_literal(";") + string_literal(BackWhite) + string_literal(StopCode);
constexpr auto LiteralOfTail = string_literal(StartCode) + string_literal(CloseAll) + string_literal(StopCode);

constexpr char const* tail = LiteralOfTail.Name;

constexpr char const* head[] = {
    LiteralOfTrace.Name,
    LiteralOfDebug.Name,
    LiteralOfInfo .Name,
    LiteralOfWarn .Name,
    LiteralOfSuccess.Name,
};

}//namespace ColorType


void AsyncLog::setColorState(bool state)
{
    m_useColor = state;
}

void AsyncLog::installMessageCallback(std::function<void(const char* data, int size)> writer,
    std::function<void()> flusher,
    std::function<void()> closer)
{
    std::unique_lock<std::mutex> mtxLock(m_mutex);

    //first close 
    auto& oldCloser = std::get<2>(m_toInstaller);
    if (oldCloser)
        oldCloser();

    std::get<0>(m_toInstaller) = std::move(writer);
    std::get<1>(m_toInstaller) = std::move(flusher);
    std::get<2>(m_toInstaller) = std::move(closer);

}

/*!
 * \brief AsyncLog::ShowMessage,纯净模式打印，无前缀，无自动换行
 * \param pattern
 */
void AsyncLog::ShowMessage(const char* pattern, ...)
{
    constexpr int cBaseSize = 256;

    char buffer[cBaseSize];
    // 处理变长参数
    va_list arglist;
    va_start(arglist, pattern);
    int dataSize = vsnprintf(buffer, sizeof(buffer), pattern, arglist);
    va_end(arglist);

    if (dataSize >= sizeof(buffer)) {//here means data too long,we need expand write buffer
        std::string strWrite;
        strWrite.resize(dataSize + 1);// with "\0"
        va_start(arglist, pattern);
        int dataSize = vsnprintf(const_cast<char*>(strWrite.data()), strWrite.size(), pattern, arglist);
        va_end(arglist);
        assert(dataSize < strWrite.size());

        Append(strWrite.data(),dataSize);
    }
    else {
        Append(buffer,dataSize);
    }
}


void AsyncLog::ShowMessage(const LogFormat& format, const char* pattern, ...)
{
    constexpr int cBaseSize = 256;
    std::string text;

    bool useColor = m_useColor; 

    if (useColor) {
        text += ColorType::head[format.m_level];
    }

    text += format.GetPrefix();//添加日志前缀

    char buffer[cBaseSize];
    // 处理变长参数
    va_list arglist;
    va_start(arglist, pattern);
    int dataSize = vsnprintf(buffer, sizeof(buffer), pattern, arglist);
    va_end(arglist);

    if (dataSize >= sizeof(buffer)) {//here means data too long,we need expand write buffer
        std::string strWrite;
        strWrite.resize(dataSize + 2);// with "\n\0"
        va_start(arglist, pattern);
        int dataSize = vsnprintf(const_cast<char*>(strWrite.data()), strWrite.size(), pattern, arglist);
        va_end(arglist);
        assert(dataSize < strWrite.size());
        strWrite[dataSize] = '\n';
        text += strWrite;
    }
    else {
        buffer[dataSize] = '\n';
        text.append(buffer, dataSize + 1);
    }

    if (useColor) {
        text += ColorType::tail;
    }

    Append(text.data(), text.size());

}

void AsyncLog::Append(const char* data, int size)
{
    std::unique_lock<std::mutex> mtxLock(m_mutex);

    if (m_currBuf->avail() > size)
    {
        m_currBuf->append(data, size);
    }
    else {
        m_vecBuf.push_back(std::move(m_currBuf));

        if (m_nextBuf)
        {
            m_currBuf = std::move(m_nextBuf);
        }
        else {
            m_currBuf = std::make_unique<Buffer>();// Rarely happens
            //m_currBuf.reset(new Buffer); 
        }
        m_currBuf->append(data, size);
        m_cv.notify_one();
    }
}


void AsyncLog::WriteThread()
{
    BufferPtr newBuffer1 = std::make_unique<Buffer>();
    BufferPtr newBuffer2 = std::make_unique<Buffer>();
    newBuffer1->bzero();
    newBuffer2->bzero();
    BufferVector buffersToWrite;
    buffersToWrite.reserve(16);
    std::chrono::milliseconds interval(m_flushInterval_ms);
    do
    {
        assert(newBuffer1 && newBuffer1->length() == 0);
        assert(newBuffer2 && newBuffer2->length() == 0);
        assert(buffersToWrite.empty());

        {
            std::unique_lock<std::mutex> mtxLock(m_mutex);
            if (m_vecBuf.empty())  // unusual usage!
            {
                m_cv.wait_for(mtxLock, interval);
            }
            m_vecBuf.push_back(std::move(m_currBuf));
            m_currBuf = std::move(newBuffer1);
            buffersToWrite.swap(m_vecBuf);
            if (!m_nextBuf)
            {
                m_nextBuf = std::move(newBuffer2);
            }
        }

        assert(!buffersToWrite.empty());

        if (buffersToWrite.size() > 25)
        {
            char buf[256];
            int size = snprintf(buf, sizeof buf, "Dropped log messages at %s, %zd larger buffers\n",
                TimeToString(TimeStamp()).data(),
                buffersToWrite.size() - 2);
            fputs(buf, stderr);
            DataWrite(buf, size);
            buffersToWrite.erase(buffersToWrite.begin() + 2, buffersToWrite.end());
        }

        for (const auto& buffer : buffersToWrite)
        {
            // FIXME: use unbuffered stdio FILE ? or use ::writev ?
            DataWrite(buffer->data(), buffer->length());
        }

        if (buffersToWrite.size() > 2)
        {
            // drop non-bzero-ed buffers, avoid trashing
            buffersToWrite.resize(2);
        }

        if (!newBuffer1)
        {
            assert(!buffersToWrite.empty());
            newBuffer1 = std::move(buffersToWrite.back());
            buffersToWrite.pop_back();
            newBuffer1->reset();
        }

        if (!newBuffer2)
        {
            assert(!buffersToWrite.empty());
            newBuffer2 = std::move(buffersToWrite.back());
            buffersToWrite.pop_back();
            newBuffer2->reset();
        }

        buffersToWrite.clear();
        DataFlush();
    } while (m_running);
    //这里之所以使用do...while 结构是为了保证程序一定会进入日志
    // 否则会出现线程还没执行到while（true）这一句，程序就已经结束了,
    // 这样会导致写入的日志没被显示出来

    DataFlush();
}

#endif


}}//namespace Log::tool


